package com.yunao.commons.serachcore.core;


import com.yunao.commons.serachcore.core.Isearch;
import com.yunao.commons.serachcore.core.bean.*;
import com.yunao.commons.serachcore.core.exception.QueryException;
import com.yunao.commons.serachcore.core.exception.SearchException;
import com.yunao.commons.serachcore.core.exception.SearchFieldException;
import com.yunao.commons.serachcore.core.exception.SearchInitException;
import com.yunao.commons.serachcore.core.help.GroupField;
import com.yunao.commons.serachcore.core.help.Sort;
import com.yunao.commons.serachcore.core.query.Query;
import com.yunao.commons.serachcore.core.utils.ResultUtils;
import com.zhongsou.search.core.SearchSystem;
import com.zhongsou.search.core.conf.Configuration;
import com.zhongsou.search.core.query.*;
import com.zhongsou.search.core.query.Term;
import com.zhongsou.search.core.query.Term.Sorter;
import com.zhongsou.search.core.query.Term.Counter;

import java.util.Arrays;
import java.util.Properties;

class IsearchImpl implements Isearch {
	
	public static void init(String host, int port) throws SearchInitException {
		Properties props = new Properties();
		props.setProperty("system.net.sock.ip", host);
		props.setProperty("system.net.sock.query.port", String.valueOf(port));
		props.setProperty("system.net.sock.index.port", String.valueOf(port + 1));

		props.setProperty("system.add.byThread", "false");
		props.setProperty("system.add.articlefile.del", "true");
		props.setProperty("system.index.articleNumInOneFile", "100");
		props.setProperty("system.index.file.dir", "format_file");
		props.setProperty("socket.timeout.second", "3000");
		props.setProperty("article.textfield.maxlength.K", "200");

		props.setProperty("special.index.fieldnames", "TX");
		props.setProperty("special.index.TX", "[TI][\r\n\t\r\n\t\t\r\n][TX]");

		props.setProperty("enum.CC", "1:财经金融,2:科技互联网,3:旅游生活,4:能源环保,5:广告传媒,6:汽车交通,7:教育人力,8:健康医疗,9:时尚娱乐,10:地产家居,11:军事时政,12:体育艺术,13:社会综合");
		props.setProperty("enum.DL", "21:中央重点,22:中央行业,23:省级重点,24:地方城市,25:商业重点,26:商业行业,27:境内其它,28:境外新闻,29:境外其它");
		props.setProperty("enum.MD", "41:新闻,42:客户端,43:政务,44:电子报,45:网站,46:视频,47:电视,48:论坛,49:贴吧,50:博客,51:微博,52:微信,53:网站|其它");
		props.setProperty("enum.EM", "61:正面,62:负面,63:中性|无正负|有正有负");
		props.setProperty("enum.MT", "71:正文|一般新闻|微博正文|微博|微信正文|微信,72:评论|新闻评论,73:主题帖|主帖,74:回帖");


		try {
			if (!SearchSystem.getAllSystem().hasNext()) {
				SearchSystem.init(new Configuration(props));
			}
		} catch (Exception e) {
			throw new SearchInitException(e);
		}
	}

	@Override
	public Long count(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.COUNT);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			return ss.count(term);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Articles query(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.QUERY);
			System.out.println("term:"+term.toString());
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			Hits hits = ss.query(term);
			System.out.println("查询结果size:"+hits.size());
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Groups group(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.GROUP);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			GroupHits hits = ss.group(term);
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Cubes cube(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.CUBE);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			CubeHits hits = ss.cube(term);
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Wordclouds wordcloud(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.WORDCLOUD);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			WordCloudHits hits = ss.wordcloud(term);
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Wordrelations wordrelation(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.WORDRELATION);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			WamHits hits = ss.wam(term);
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Clusters cluster(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.CLUSTER);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			ClusterHits hits = ss.cluster(term);
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			throw new SearchException();
		}
	}

	@Override
	public Articles distinct(Query query) throws SearchException {
		SearchSystem ss;
		try {
			ss = SearchSystem.getInstance();
		} catch (Exception e) {
			throw new SearchInitException();
		}
		
		Term term;
		try {
			term = buildTerm(ss, query, IsearchType.DISTINCT);
		} catch (Exception e) {
			throw new SearchFieldException();
		}
		
		try {
			Hits hits = ss.distinct(term);
			return ResultUtils.parse(hits, query);
		} catch (Exception e) {
			e.printStackTrace();
			throw new SearchException();
		}
	}
	
	private static final long TIME_GROUP_BEGIN = (24 * 3 + 16) * 3600 * 1000l;

	private Term buildTerm(SearchSystem ss, Query query, IsearchType iType) throws Exception {
		Term term = newTerm(ss, query, iType);
		termSetText(term, "TI", query.getTitle());
		termSetText(term, "TX", query.getKeywords());
		termSetIds(term, query.getIds());
		termSetTime(term, query);
		termSetOffset(term, query.getOffset());
		termSetDomestic(term, query.getDomestic());
		termSetNotIndexedText(term, "AU", query.getAuthor());
		termSetNotIndexedText(term, "HO", query.getHost());
		termSetNotIndexedText(term, "SL", query.getUrl());
		termSetNotIndexedText(term, "NR", query.getNr());
		termSetNotIndexedText(term, "NS", query.getNs());
		termSetNotIndexedText(term, "NT", query.getNt());
		termSetCategory(term, "LC", query.getLocation());
		termSetCategory(term, "DM", query.getDomain());
		termSetCategory(term, "AA", query.getNewsSource());
		termSetCategory(term, "MD", query.getMediaType());
		termSetCategory(term, "MT", query.getMessageType());
		termSetCategory(term, "EM", query.getEmotion());
		termSetCategory(term, "DL", query.getDomainLevel());
		termSetCategory(term, "CC", query.getChannel());
		if (IsearchType.QUERY == iType) {
			termSetLimit(term, query.getLimit(), 20);
		} else if (IsearchType.GROUP == iType) {
			termSetGroup(term, query.getGroup());
			termSetLimit(term, query.getLimit(), 0);
		} else if (IsearchType.CUBE == iType) {
			termSetCube(term, query.getGroup(), query.getGroup2());
			termSetLimit(term, query.getLimit(), 0);
		} else if (IsearchType.WORDCLOUD == iType) {
			termSetWordcloud(term, query.getMiningField(), 
					query.getMaxWord());
			termSetLimit(term, query.getLimit(), 500);
		} else if (IsearchType.WORDRELATION == iType) {
			termSetWordrelation(term, query.getMiningField(), 
					query.getMaxWord());
			termSetLimit(term, query.getLimit(), 500);
		} else if (IsearchType.CLUSTER == iType) {
			termSetCluster(term, query.getMiningField(), 
					query.getDistance(), query.getMaxGroup());
			termSetLimit(term, query.getLimit(), 1000);
		} else if (IsearchType.DISTINCT == iType) {
			term.setDistinct(query.getMiningField());
			termSetLimit(term, query.getLimit(), 20);
		}
		return term;
	}
	
	private void termSetCluster(Term term, String miningField, double distance, int maxGroup) {
		term.setCluster(miningField, distance == 0 ? 0.5 : distance, 
				5, 4, 10, maxGroup == 0 ? 20 : maxGroup, 20);
	}
	
	private void termSetWordrelation(Term term, String miningField, int maxWord) {
		term.setWAM(miningField, maxWord == 0 ? 200 : maxWord, 20);
	}
	
	private void termSetWordcloud(Term term, String miningField, int maxWord) {
		term.setWordCloud(miningField, maxWord == 0 ? 200 : maxWord, 20);
	}
	
	private void termSetCube(Term term, GroupField field1, GroupField field2) throws Exception {
		if (field1 == null || field2 == null)
			throw new Exception("Cube field is null");
		
		if (field1 == GroupField.HOUR 
				|| field1 == GroupField.DAY 
				|| field1 == GroupField.WEEK) {
			term.setCube("RQ", TIME_GROUP_BEGIN, 
					getTimeGroupStep(field1), 
					parseGroupField(field2));
		} else {
			term.setCube(parseGroupField(field1), 
					parseGroupField(field2));
		}
	}

	private void termSetGroup(Term term, GroupField gField) throws QueryException, Exception {
		if (gField == null)
			throw new QueryException("Group field is null");
		
		if (gField == GroupField.HOUR 
				|| gField == GroupField.DAY 
				|| gField == GroupField.WEEK) {
			term.setGroup("RQ", TIME_GROUP_BEGIN, 
					getTimeGroupStep(gField));
		} else {
			term.setGroup(parseGroupField(gField));
		}
	}
	
	private void termSetLimit(Term term, int limit, int defaultLimit) {
		int n = limit <= 0 ? defaultLimit : limit;
		if (n > 0) {
			term.setAskNum(n);
		}
	}
	
	private String parseGroupField(GroupField gField) {
		String res = null;
		if (gField == GroupField.AUTHOR) {
			res = "AU";
		} else if (gField == GroupField.DOMAIN) {
			res = "DM";
		} else if (gField == GroupField.HOST) {
			res = "HO";
		} else if (gField == GroupField.MEDIA_TYPE) {
			res = "MD";
		} else if (gField == GroupField.CHANNEL) {
			res = "CC";
		} else if (gField == GroupField.EMOTION) {
			res = "EM";
		} else if (gField == GroupField.AUTHOR_TYPE) {
			res = "AT";
		} else if (gField == GroupField.LOCATION) {
			res = "LC";
		} else if (gField == GroupField.DOMAIN_LEVEL) {
			res = "DL";
		}
		return res;
	}
	
	private long getTimeGroupStep(GroupField gField) {
		long t = 0;
		if (gField == GroupField.HOUR) {
			t = 3600 * 1000;
		} else if (gField == GroupField.DAY) {
			t = 24 * 3600 * 1000;
		} else if (gField == GroupField.WEEK) {
			t = 7 * 24 * 3600 * 1000;
		}
		return t;
	}
	
	private void termSetText(Term term, String field, String value) throws Exception {
		if (isNotEmpty(value)) {
			term.andText(field, value);
		}
	}

	private void termSetIds(Term term, long[] ids) throws Exception {
		if (ids != null && ids.length > 0) {
			term.in("ID", ids);
		}
	}

	private void termSetOffset(Term term, int offset) {
		if (offset > 0) {
			term.setStart(offset);
		}
	}

	private void termSetDomestic(Term term, int domestic) throws Exception {
		if (domestic != 0) {
			term.equal("TE", domestic == 2 ? Byte.parseByte("1") : Byte.parseByte("0"));
		}
	}
	
	
	// lc dm aa sd mt em dl cc
	private void termSetCategory(Term term, String field, String[] values) throws Exception {
		String[] array = trimAndDistinct(values);
		if (array != null && array.length > 0) {
			term.andCategory(field, array);
		}
	}
	
	// au ho aa sl nr nl nt
	private void termSetNotIndexedText(Term term, String field, String[] values) throws Exception {
		String[] array = trimAndDistinct(values);
		if (array != null && array.length > 0) {
			term.andNotIndexedText(field, array);
		}
	}

	private String[] trimAndDistinct(String[] array) {
		if (array == null || array.length == 0) {
			return array;
		}
		
		return Arrays.stream(array)
				.filter(this::isNotEmpty)
				.map(String::trim)
				.distinct()
				.toArray(String[]::new);
	}

	// 设置时间，默认最近1个月
	private void termSetTime(Term term, Query query) throws Exception {
		String field = "RQ";
		if (query.getGreaterEqual() > 0) {
			term.greatEqual(field, query.getGreaterEqual());
		} else if (query.getGreaterThan() > 0) {
			term.greatThan(field, query.getGreaterThan());
		} else if (query.getBetween() != null) {
			term.between(field, query.getBetween()[0], query.getBetween()[1]);
		} else {
//			long t = LocalDate.now().plusDays(3).atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000;
//			term.greatEqual(field, t);
		}
	}

	private boolean isNotEmpty(String v) {
		return !(v == null || v.trim().isEmpty());
	}
	
	private Term newTerm(SearchSystem ss, Query query, IsearchType iType) {
		Sorter sorter = newSorter(query, iType);
		Term term = new Term(ss, sorter, Counter.exact);
		if (iType == IsearchType.QUERY && Sorter.field_desc.getV() == sorter.getV()) {
			term.orderByField(sorter, getOrderField(query.getSort()));
		}
		return term;
	}

	// 排序字段名称
	private String getOrderField(Sort sort) {
		String res = null;
		if (Sort.VIEWS == sort) {
			res = "VN";
		} else if (Sort.COMMENTS == sort) {
			res = "RN";
		} else if (Sort.LIKES == sort) {
			res = "ZN";
		}
		return res;
	}

	// 排序
	private Sorter newSorter(Query query, IsearchType t) {
		Sorter sorter;
		switch (t) {
		case COUNT:
		case GROUP:
		case CUBE:
			sorter = Sorter.date;
			break;
		case DISTINCT:
			sorter = Sorter.COPIES;
			break;
		case WORDCLOUD:
		case WORDRELATION:
		case CLUSTER:
			sorter = Sorter.rela;
			break;

		case QUERY:
		default:
			if (Sort.DATE == query.getSort())
				sorter = Sorter.date;
			else if (Sort.DATE_ASC == query.getSort())
				sorter = Sorter.date_asc;
			else if (Sort.RELA == query.getSort())
				sorter = Sorter.rela;
			else if (Sort.RANDOM == query.getSort())
				sorter = Sorter.RANDOM;
			else if (Sort.VIEWS == query.getSort() 
					|| Sort.COMMENTS == query.getSort() 
					|| Sort.LIKES == query.getSort())
				sorter = Sorter.field_desc;
			else
				sorter = Sorter.date;
			break;
		}
		return sorter;
	}

	protected enum IsearchType {
		COUNT, QUERY, GROUP, CUBE, WORDCLOUD, WORDRELATION, CLUSTER, DISTINCT;
	}

}
